<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>雷达图</title>
  <script type="text/javascript" src="http://d3js.org/d3.v5.min.js"></script>
</head>
<style>
  .radar-line:hover {
    fill-opacity: 0.5;
    stroke-width: 3px;
  }

</style>

<body>
  <svg width="800" height="800"></svg>
  <script type="module">
    const svg = d3.select("svg");
    const width = svg.attr("width");
    const height = svg.attr("height");
    const margin = { left: 60, top: 60, right: 60, bottom: 60 };
    const innerWidth = width - margin.right - margin.left;
    const innerHeight = height - margin.top - margin.bottom;
    const outerRadius = Math.min(innerWidth, innerHeight) * 0.5

    const maingroup = svg.append("g")
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");


    const data = [
      {
        className: '甲班',
        chinese: 88,
        math: 92,
        physics: 90,
        chemistry: 88,
        english: 95
      },
      {
        className: '乙班',
        chinese: 67,
        math: 78,
        physics: 80,
        chemistry: 72,
        english: 74
      },
      {
        className: '丙班',
        chinese: 77,
        math: 83,
        physics: 68,
        chemistry: 69,
        english: 65
      },
      {
        className: '丁班',
        chinese: 72,
        math: 67,
        physics: 62,
        chemistry: 67,
        english: 68
      }
    ]

    const names = {
      chinese: '语文',
      math: '数学',
      physics: '物理',
      chemistry: '化学',
      english: '英语'
    }

    const keys = Object.keys(data[0]).slice(1);
    console.log(keys);
    const series = data.map(d => {
      return keys.map(key => {
        return {
          key: key,
          value: d[key],
          className: d.className,
        }
      })
    })
    console.log(series);

    /**
     * 坐标轴 - 角度和类别对应
     */
    const xScale = d3.scaleBand()
      .range([0, 2 * Math.PI])
      .domain(keys)
      .align(0)

    const maxValue = d3.max(series, d => d3.max(d, item => item.value))

    // 半径和数值对应
    const yScale = d3.scaleLinear()
      .range([0, outerRadius])
      .domain([0, maxValue + 10]);

    console.log(yScale.ticks(10));

    /**
     * 刻度为圈圈 + 刻度值  -  y轴 - 值
     */
    let yTickg = maingroup.append("g")
      .attr('text-anchor', 'start')
      .selectAll("g")
      .data(yScale.ticks(10).reverse())
      .join("g");

    // 刻度圈
    yTickg.append("circle")
      .attr("fill", '#ddd')
      .attr('fill-opacity', 0.3)
      .style("stroke", '#999')
      .style("stroke-width", 1)
      .attr("r", yScale); //函数，接收一个参数d
    // .attr("r", d => yScale(d)) //作用同上

    // 刻度值背景 - 白
    yTickg
      .append('text')
      .attr('x', 6)
      .attr('y', function (d) {
        // console.log("d:", d);
        return -yScale(d)
      })
      .attr('dy', '0.35em')
      .attr('fill', 'none')
      .attr('stroke', '#fff')
      .attr('stroke-width', 5)
      .text(yScale.tickFormat(10, 'r')) //函数，接收d参数，返回向上最近整数

    // 刻度值
    yTickg.append("text")
      .attr("class", "tick")
      .attr('x', 6)
      .attr("y", d => -yScale(d))
      .attr("dy", '0.35em')
      .attr("fill", 'black')
      .style("stroke", "none")
      .attr('font-size', '16px')
      .text(yScale.tickFormat(10, 'r')); //函数，接收d参数，返回向上最近整数

    /**
     * 添加学科类型  -- 充当x轴数据
     */
    let types = keys.map(key => {
      return [
        { angle: 0, radius: 0 },
        { angle: xScale(key), radius: yScale(maxValue + 10) }
      ]
    })
    console.log("types", types);


    const lineRadial = d3
      .lineRadial()
      .angle(function (d) {
        // console.log("d", d);
        return d.angle
      })
      .radius(function (d) {
        return d.radius
      })
      .curve(d3.curveLinearClosed)

      console.log(lineRadial(types[0]));

    // 类别分割线条
    let tickg = maingroup
      .append("g")
      .attr("class", 'types')
      .selectAll('.tick') // 绘画所有轴线条
      .data(types)
      .enter()
      .append('g')
      .attr('class', 'tick');

    tickg
      .append('path') // 开始绘画所有的轴线条
      .attr('class', 'tick-line')
      .style('stroke', '#fff')
      .style('stroke-width', 2)
      .attr('fill', 'none')
      .attr('d', lineRadial);

    // 类别名称
    maingroup.append("g")
      .attr("class", "tick-type-name")
      .selectAll(".tick-type")
      .data(keys)
      .join("text")
      .attr("x", (d) => {
        return Math.sin(xScale(d)) * (outerRadius + 10)
      })
      .attr("y", (d) => {
        return -Math.cos(xScale(d)) * (outerRadius + 10)
      })
      .attr("text-anchor", (d) => {
        return xScale(d) > Math.PI ? "end" : "start"
      })
      .attr("fill", "black")
      .text(d => names[d])


    /**
     * 正餐 - 雷达图
     */
    // 添加动态效果
    let defs = maingroup.append("defs");
    defs
      .selectAll("clipPath")
      .data(d3.range(data.length))
      .join("clipPath")
      .attr('id', function (d, i) {
        return 'clip_' + i
      })
      .append("circle")
      .attr("r", 0)
      .transition()
      .duration(500)
      .delay(function (d, i) {
        return i * 500
      })
      .attr('r', outerRadius)

    let colorScale = d3.scaleOrdinal(d3.schemeCategory10) // 通用线条的颜色

    // 添加雷达的
    let serie = maingroup
      .selectAll('.serie') // 绘画雷达线条
      .data(series)
      .enter()
      .append('g')
      .attr('class', 'serie')
      .attr('clip-path', function (d, i) {
        return 'url(#clip_' + i + ')'
      })

    serie
      .append('path') // 开始绘画雷达线条
      .attr('class', 'radar-line')
      .style('stroke', function (d) {
        return colorScale(d[0].className)
      })
      .attr('fill', function (d) {
        return colorScale(d[0].className)
      })
      .attr('fill-opacity', 0.2)
      .attr(
        'd',
        d3
          .lineRadial()
          .angle(function (d) {
            return xScale(d.key)
          })
          .radius(function (d) {
            return yScale(d.value)
          })
          .curve(d3.curveLinearClosed)
      )
      .append('title') // 输出Title，mouseover显示
      .text(function (d) {
        let str = d
          .map(t => {
            return names[t.key] + ': ' + t.value + '分'
          })
          .join('\n')
        return d[0].className + '\n' + str
      })

  </script>
</body>

</html>
